package de.umr.fb12.vs.os;

import static de.umr.fb12.vs.os.OpCodes.ADD;
import static de.umr.fb12.vs.os.OpCodes.AND;
import static de.umr.fb12.vs.os.OpCodes.ARGT_MEM;
import static de.umr.fb12.vs.os.OpCodes.ARGT_REG;
import static de.umr.fb12.vs.os.OpCodes.ARGT_VAL;
import static de.umr.fb12.vs.os.OpCodes.DIV;
import static de.umr.fb12.vs.os.OpCodes.HLT;
import static de.umr.fb12.vs.os.OpCodes.INT;
import static de.umr.fb12.vs.os.OpCodes.JEQ;
import static de.umr.fb12.vs.os.OpCodes.JGE;
import static de.umr.fb12.vs.os.OpCodes.JGT;
import static de.umr.fb12.vs.os.OpCodes.JLE;
import static de.umr.fb12.vs.os.OpCodes.JLT;
import static de.umr.fb12.vs.os.OpCodes.JMP;
import static de.umr.fb12.vs.os.OpCodes.JNE;
import static de.umr.fb12.vs.os.OpCodes.MOD;
import static de.umr.fb12.vs.os.OpCodes.MOV;
import static de.umr.fb12.vs.os.OpCodes.MUL;
import static de.umr.fb12.vs.os.OpCodes.NOT;
import static de.umr.fb12.vs.os.OpCodes.OR;
import static de.umr.fb12.vs.os.OpCodes.SHL;
import static de.umr.fb12.vs.os.OpCodes.SHR;
import static de.umr.fb12.vs.os.OpCodes.SOP;
import static de.umr.fb12.vs.os.OpCodes.SUB;
import static de.umr.fb12.vs.os.OpCodes.XOR;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

class Assembler {
	private final static int V = 1 << 31;
	private final static int R = 1 << 30;
	private final static int M = 1 << 29;
	private final static int RM = R | M;
	private final static int VRM = V | R | M;

	// the opcode map maps the mnemocode to an array with the following content:
	// opcode (as defined in OpCodes), argType, argType, ...
	// where each argType entry stands for one argument and is one of V, RM and
	// VRM
	private final static Map<String, int[]> opcodes = new HashMap<String, int[]>();

	static {
		opcodes.put("HLT", new int[] { HLT });
		opcodes.put("INT", new int[] { INT, V });
		opcodes.put("SOP", new int[] { SOP, V });
		opcodes.put("JMP", new int[] { JMP, M });
		opcodes.put("JLT", new int[] { JLT, RM, VRM, M });
		opcodes.put("JLE", new int[] { JLE, RM, VRM, M });
		opcodes.put("JEQ", new int[] { JEQ, RM, VRM, M });
		opcodes.put("JNE", new int[] { JNE, RM, VRM, M });
		opcodes.put("JGT", new int[] { JGT, RM, VRM, M });
		opcodes.put("JGE", new int[] { JGE, RM, VRM, M });
		opcodes.put("MOV", new int[] { MOV, RM, VRM });
		opcodes.put("ADD", new int[] { ADD, RM, VRM });
		opcodes.put("SUB", new int[] { SUB, RM, VRM });
		opcodes.put("MUL", new int[] { MUL, RM, VRM });
		opcodes.put("DIV", new int[] { DIV, RM, VRM });
		opcodes.put("MOD", new int[] { MOD, RM, VRM });
		opcodes.put("NOT", new int[] { NOT, RM });
		opcodes.put("AND", new int[] { AND, RM, VRM });
		opcodes.put("OR", new int[] { OR, RM, VRM });
		opcodes.put("XOR", new int[] { XOR, RM, VRM });
		opcodes.put("SHR", new int[] { SHR, RM, VRM });
		opcodes.put("SHL", new int[] { SHL, RM, VRM });
	}

	private Pattern REGISTER = Pattern.compile("[Rr]\\d{1,2}");
	private Pattern DECVAL = Pattern.compile("\\d{1,15}");
	private Pattern LABEL = Pattern.compile("\\p{Alpha}\\w{0,20}");

	private void debug(String format, Object... arguments) {
	}

	void assemble(File source, File target) {
		try {
			int[] bytecode = assemble(source);
			ObjectOutputStream output = new ObjectOutputStream(
					new FileOutputStream(target));
			// magic
			output.writeShort(4711);
			output.writeInt(bytecode.length);
			for (int i = 0; i < bytecode.length; i++)
				output.writeInt(bytecode[i]);
			output.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	int[] assemble(File file) {
		try {
			CharArrayWriter caw = new CharArrayWriter((int) file.length());
			FileReader rf = new FileReader(file);
			char[] buf = new char[1000];
			int len = 0;

			while ((len = rf.read(buf)) > 0) {
				caw.write(buf, 0, len);
			}

			rf.close();
			caw.close();
			return assemble(new String(caw.toCharArray()));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	int[] assemble(String code) {
		int[] assembly = new int[4096];
		int address = 0;

		Map<String, Integer> labelAddressMap = new HashMap<String, Integer>();
		Map<Integer, String> labelPlaceholderMap = new HashMap<Integer, String>();

		String[] lines = code.split("\n");
		for (int lineNo = 1; lineNo <= lines.length; lineNo++) {
			String line = lines[lineNo - 1];
			debug("LINE:  %s%n", line);

			// skip comments
			if (line.trim().startsWith("//"))
				continue;

			// skip blank lines
			if (line.trim().length() == 0)
				continue;

			if (line.contains(":")) {
				String[] labelCmd = line.trim().split(":");

				String label = labelCmd[0].trim();
				debug("LABEL: %s at %d%n", label, address);
				labelAddressMap.put(label, address);

				if (labelCmd.length > 1)
					line = labelCmd[1];
				else
					continue;
			}

			String[] mnemoArgs = line.trim().split("\\s", 2);

			String mnemo = mnemoArgs[0].toUpperCase();
			String[] args;
			if (mnemoArgs.length > 1) {
				String[] rawArgs = mnemoArgs[1].trim().split(",");
				args = new String[rawArgs.length];
				for (int i = 0; i < args.length; i++) {
					args[i] = rawArgs[i].trim();
					if (args[i].length() == 0)
						System.err.printf("illegal argument in line %d%n",
								lineNo);
				}
			} else
				args = new String[0];

			debug("MNEMO: %s%n", mnemo);
			for (String arg : args)
				debug("ARG:   %s%n", arg);

			if (mnemo.equals(".VAL")) {
				if (args.length < 1)
					System.err.printf("missing value in line %d%n", lineNo);
				assembly[address] = Integer.parseInt(args[0]);
				address++;
			} else if (opcodes.containsKey(mnemo)) {
				int[] odata = opcodes.get(mnemo);
				assembly[address] = odata[0];
				if (odata.length > 1)
					for (int i = 1; i < odata.length; i++) {
						if (args.length < i - 1)
							System.err.printf("missing argument in line %d%n",
									lineNo);
						String rawArg = args[i - 1];
						int arg;
						int argType;
						if (REGISTER.matcher(rawArg).matches()) {
							if ((odata[i] & R) == 0)
								System.err
										.printf("illegal argument: register as argument %d in line %d%n",
												i, lineNo);
							arg = Integer.parseInt(rawArg.substring(1));
							argType = ARGT_REG;
						} else if (LABEL.matcher(rawArg).matches()) {
							if ((odata[i] & M) == 0)
								System.err
										.printf("illegal argument: label as argument %d in line %d%n",
												i, lineNo);
							labelPlaceholderMap.put(address + i, rawArg);
							arg = -1; // just for the compiler
							argType = ARGT_MEM;
						} else if (DECVAL.matcher(rawArg).matches()) {
							if ((odata[i] & V) == 0)
								System.err
										.printf("illegal argument: value as argument %d in line %d%n",
												i, lineNo);
							arg = Integer.parseInt(rawArg);
							argType = ARGT_VAL;
						} else {
							System.err.printf(
									"failed to pars argument %s in line %d%n",
									rawArg, lineNo);
							;
							continue;
						}
						assembly[address + i] = arg;
						assembly[address] |= argType << (30 - (i * 2));
					}
				assembly[address] |= (odata.length - 1) << 30;
				address += odata.length;
			} else {
				System.err.printf("illegal mnemo code %s in line %d%n", mnemo,
						lineNo);
				System.out.println(mnemo);
			}
		}

		// add addresses
		for (Integer addr : labelPlaceholderMap.keySet()) {
			String label = labelPlaceholderMap.get(addr);
			if (!labelAddressMap.containsKey(label))
				System.err.printf("undefined label %s%n", label);
			assembly[addr] = labelAddressMap.get(label);
		}

		int[] result = new int[address];
		System.arraycopy(assembly, 0, result, 0, address);

		return result;
	}

}
